package com.suxin.utils;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;
import java.util.function.Supplier;

/**
 * @Classname RetryUtils
 * @Description [ RetryUtils ]
 * @Author Tang
 * @Date 2020/11/17 10:38
 * @Created by ASUS
 */
@Slf4j
public class RetryUtils {

    public static <T> T executeWithRetry(Callable<T> callable, int retryTimes, long sleepSecond) {
        Retry retry = new Retry();
        return retry.doRetry(callable, retryTimes, sleepSecond);
    }

    public static void asyncExecuteWithRetry() {
        AsyncRetry asyncRetry = new AsyncRetry();
    }

    public static class Retry {

        public <T> T doRetry(Callable<T> callable, int retryTimes, long sleepSecond) {
            for (int i = 0; i < retryTimes; i++) {
                try {
                    return call(callable);
                } catch (Exception e) {
                    try {
                        TimeUnit.SECONDS.sleep(sleepSecond);
                    } catch (InterruptedException interruptedException) {
                        interruptedException.printStackTrace();
                    }
                }

            }
            return null;
        }

        protected <T> T call(Callable<T> callable) throws Exception {
            return callable.call();
        }

    }

    public static class AsyncRetry extends Retry {

        @Override
        protected <T> T call(Callable<T> callable) throws Exception {
            FutureTask<T> futureTask = new FutureTask<>(callable);
            new Thread(futureTask).start();
            return futureTask.get();
        }

    }

    public static class Executor {
        @Getter
        private static ExecutorService executorService = Executors.newCachedThreadPool();

        public static void exec(Runnable runnable) {
            executorService.execute(runnable);
        }
    }

    private static int MAX_RETRY = 5;

    public <T> Object syncRetry(Supplier<T> supplier) {
        return syncRetry0(supplier, 0);
    }

    private <T> Object syncRetry0(Supplier<T> supplier, int i) {
        CompletableFuture<Object> future1 = new CompletableFuture<>();
        CountDownLatch latch = new CountDownLatch(MAX_RETRY);
        CompletableFuture<Boolean> futureFinishOrRetriesDown = new CompletableFuture<>();
        // 有一个执行成功
        Executor.exec(() -> {
            try {
                log.info("future1 join...start");
                future1.join();
                futureFinishOrRetriesDown.complete(true);
                log.info("future1 join...end");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        // 全部失败
        Executor.exec(() -> {
            try {
                log.info("latch wait...start");
                latch.await();
                future1.complete(null);
                futureFinishOrRetriesDown.complete(true);
                log.info("latch wait...end");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        Executor.exec(() -> {
            retryWork(latch, future1, supplier, i);
        });
        log.info("futureFinishOrRetriesDown.join...start");
        // 等待 latch 或者 future1 任意一个结束
        futureFinishOrRetriesDown.join();
        log.info("futureFinishOrRetriesDown.join...end");
        // 必须同步获取结果
        return future1.getNow(null);
    }

    private <T> void retryWork(CountDownLatch latch,
                               CompletableFuture<Object> future1,
                               Supplier<T> supplier, int i) {
            try {
                Object result = supplier.get();
                // future1 可能永远无法得到结果
                log.info("方法执行完成...");
                future1.complete(result);
            } catch (Exception e) {
                log.info("第{}重试执行失败", i);
                if ((i + 1) <= MAX_RETRY) {
                    retryWork(latch, future1, supplier, i + 1);
                }
            } finally {
                latch.countDown();
            }

    }

    public static void main(String[] args) {


    }

}